package com.example.aboutwork.nonblocking;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.example.aboutwork.nonblocking.future.Future;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
public class FutureFunc1<A> {

    private final Function<ExecutorService, Future<A>> func;


    private Future<A> apply(ExecutorService es) {
        return func.apply(es);
    }

    /*同步求值*/
    A run(ExecutorService es) throws InterruptedException {
        AtomicReference<A> reference = new AtomicReference<>();
        CountDownLatch latch = new CountDownLatch(1);
        Future<A> future = func.apply(es);
        future.apply(a -> {
            reference.set(a);
            latch.countDown();
            return null;
        });
        latch.await();
        return reference.get();
    }

    /*异步求值*/
    private static Void eval(ExecutorService es, Supplier<Void> supplier) {
        try {
            return es.submit(supplier::get).get();
        } catch (Exception e) {
            log.error("eval求值出错", e);
            throw new RuntimeException(e);
        }
    }

    public static <A> FutureFunc1<A> unit(A a) {
        return new FutureFunc1<>(es -> f -> f.apply(a));
    }

    public static <A> FutureFunc1<A> lazyUnit(Supplier<A> aSupplier) {
        return fork(() -> unit(aSupplier.get()));
    }

    public static <A> FutureFunc1<A> delay(A a) {
        return new FutureFunc1<>(es -> f -> f.apply(a));
    }

    public static <A, B> Function<A, FutureFunc1<B>> asyncF(Function<A, B> f) {
        return (A a) -> new FutureFunc1<B>(es -> funcB -> f.andThen(funcB).apply(a));
    }

    private static <A> FutureFunc1<A> fork(Supplier<FutureFunc1<A>> lazyFuncA) {
        return new FutureFunc1<>(es -> f -> eval(es, () -> {
            Future<A> aFuture = lazyFuncA.get().apply(es);
            return aFuture.apply(f);
        }));
    }

    FutureFunc1<A> async(Future<A> fa) {
        return new FutureFunc1<>(es -> fa);
    }


    public <B, C> FutureFunc1<C> map2(FutureFunc1<B> funcB,
                                      BiFunction<A, B, C> f) {
        return new FutureFunc1<>(es -> funcC -> {
            try {
                FutureFunc1<A> forkA = fork(() -> this);
                FutureFunc1<B> forkB = fork(() -> funcB);
                A a = forkA.run(es);
                B b = forkB.run(es);
                C c = f.apply(a, b);
                return funcC.apply(c);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    public <B> FutureFunc1<B> map(Function<A, B> f) {
        return new FutureFunc1<>(es -> funcB -> {
            Future<A> aFuture = func.apply(es);
            Function<A, Void> aVoidFunc1 = (A a) -> eval(es, () -> f.andThen(funcB).apply(a));
            return aFuture.apply(aVoidFunc1);
        });
    }


    public <B> FutureFunc1<B> flatMap(Function<A, FutureFunc1<B>> f) {
        return new FutureFunc1<>(es -> bFunc -> func.apply(es).apply(a -> f.apply(a).apply(es).apply(bFunc)));
    }


    public FutureFunc1<A> choice(FutureFunc1<Boolean> booleanFunc,
                                 FutureFunc1<A> choiceFunc,
                                 FutureFunc1<A> notChoiceFunc) {
        return new FutureFunc1<>(es -> funcA -> booleanFunc.apply(es)
                .apply(b -> {
                    if (b) {
                        return eval(es, () -> choiceFunc.apply(es).apply(funcA));
                    } else {
                        return eval(es, () -> notChoiceFunc.apply(es).apply(funcA));
                    }
                }));
    }

    public <B> FutureFunc1<List<B>> parMap(List<A> as, Function<A, B> f) {
        List<FutureFunc1<B>> bFunctions = as.stream().map(a -> asyncF(f).apply(a)).collect(Collectors.toList());

        return sequence(bFunctions);
    }

    public <B> FutureFunc1<List<B>> parMapBalance(List<A> as, Function<A, B> f) {
        List<FutureFunc1<B>> bFunctions = as.stream().map(a -> asyncF(f).apply(a)).collect(Collectors.toList());
        return sequenceBalance(bFunctions);
    }


    public static <A> FutureFunc1<List<A>> sequenceRight(List<FutureFunc1<A>> as) {
        if (Objects.isNull(as))
            return unit(null);
        FutureFunc1<A> first = as.get(0);
        List<FutureFunc1<A>> remains = as.subList(1, as.size());
        return first.map2(fork(() -> sequenceBalance(remains)), (a, as1) -> {
            List<A> result = Lists.newArrayList(as1);
            result.add(a);
            return result;
        });
    }

    public static <A> FutureFunc1<List<A>> sequence(List<FutureFunc1<A>> as) {
        return sequenceBalance(as);
    }


    public static <A> FutureFunc1<List<A>> sequenceBalance(List<FutureFunc1<A>> as) {


        if (CollectionUtils.isEmpty(as)) {
            return unit(Lists.newArrayList());
        }

        if (as.size() == 1) {
            return as.get(0).map(Lists::newArrayList);
        }

        List<FutureFunc1<A>> leftList = as.subList(0, as.size() / 2);
        List<FutureFunc1<A>> rightList = as.subList(as.size() / 2, as.size());

        return sequenceBalance(leftList).map2(sequenceBalance(rightList), (as1, as2) -> {
            List<A> list = Lists.newArrayList(as1);
            list.addAll(as2);
            return list;
        });

    }


}
